package x.ovo.wechat.bot;

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import x.ovo.wechat.bot.common.entity.Contact;
import x.ovo.wechat.bot.common.entity.event.MessageEvent;
import x.ovo.wechat.bot.common.entity.message.TextMessage;
import x.ovo.wechat.bot.common.event.Event;
import x.ovo.wechat.bot.common.event.EventListener;

import java.io.File;
import java.time.Instant;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "ChatGPT")
@SuppressWarnings("DuplicatedCode")
public class AIListener extends EventListener<MessageEvent<TextMessage>> {

    private static final String GEMINI_URL = "https://api.alcex.cn/API/gemini";
    private static final String GROQ_URL = "https://api.alcex.cn/API/ai/grop.php";
    private static final String GPT4_URL = "https://api.alcex.cn/API/gpt-4/v2.php";
    private static final String DUCKDUCKGO_URL = "https://api.alcex.cn/API/gpt-3.5/v4.php";
    // 会话上下文保留5分钟
    private static final TimedCache<String, List<JSONObject>> CACHE = CacheUtil.newTimedCache(TimeUnit.MINUTES.toMillis(5));
    private static final List<String> KEYWORDS = List.of("gpt", "groq", "gpt4", "gemini");
    private static final List<String> CLEAR_KEYWORDS = List.of("clear", "清空", "清空上下文", "清空会话", "清空聊天", "清空对话", "清除会话", "清除上下文", "清除聊天", "清除对话");
    private static final Map<String, String> FREE_TALK = new HashMap<>(16);

    static {
        CACHE.schedulePrune(TimeUnit.MINUTES.toMillis(1));
    }

    // 获取缓存的会话上下文，如果不存在则返回一个空的JSONObject
    private static List<JSONObject> getCache(String key) {
        return CACHE.get(key, LinkedList::new);
    }

    @Override
    public boolean support(Event<?> event) {
        return event instanceof MessageEvent
                && event.getSource() instanceof TextMessage
                && StrUtil.isNotBlank(((MessageEvent<?>) event).getContent());
    }

    @Override
    public void onEvent(MessageEvent<TextMessage> event) {
        TextMessage message = event.getSource();
        Contact from = message.getFromUser();
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .build();
        // 通过用户名查询该用户是否开启自由对话，如果开启则在消息正文前添加模型名称
        String content = event.getContent().toLowerCase();
        if (!content.startsWith("ai ") && FREE_TALK.containsKey(from.getUserName()))
            content = FREE_TALK.get(from.getUserName()) + " " + content;
        // 拆分模型名称与正文
        List<String> list = StrUtil.split(content, ' ', 2);

        switch (list.get(0)) {
            case "ai" -> this.handleHelp(list.size() > 1 ? list.get(1) : "help", message);
            case "gpt" -> this.handleDuckDuckGo(list.get(1), from, client);
            case "groq" -> this.handleGroq(list.get(1), from, client);
            case "gpt4" -> this.handleGPT4(list.get(1), from, client);
            case "gemini" -> this.handleGemini(list.get(1), from, client);
            case "draw" -> handleDraw(list.get(1), from);
            default -> { }
        }

    }

    private void handleHelp(String content, TextMessage message) {
        Contact fromUser = message.getFromUser();
        if (StrUtil.isBlank(content) || content.equals("help") || content.equals("帮助")) {
            String help = """
                    欢迎使用AI小助手，本助手现在已接入groq、gpt4、gpt 3.5、gemini大模型和百度绘图，推荐使用groq。
                    使用方法：[模型名称] 问题
                    例：groq 你能做什么？
                    模型名称包括："groq", "gpt", "gpt4", "gemini"
                    本助手支持上下文，默认会话上下文保存5分钟，可手动清空上下文，方法：[模型名称] [清空上下文关键词]
                    例子：groq clear
                    关键词包括："clear", "清空", "清空上下文", "清空会话", "清空聊天", "清空对话", "清除会话", "清除上下文", "清除聊天", "清除对话"
                    \n
                    可针对固定模型开启自由对话，避免每次都需要输入模型名称，方法：ai [start|开启] [模型名称]
                    例：ai start groq
                    关闭自由对话：ai [end|结束]
                    例：ai end
                    查询自由对话状态：ai [status|状态]
                    例：ai status
                    AI绘图使用方法(注意空格)：draw [提示词] [图片高度][ |x][图片宽度]
                    例1：draw 猫猫，水彩风格，二次元 1920x1080
                    例2：draw 猫猫
                    例3：draw 猫猫 512 512
                    默认图片大小为512x512
                    """;
            this.context.getApi().sendText(fromUser.getUserName(), help);
            return;
        }

        try {
            String[] split = content.split(" ");
            String flag = split[0];
            String model = split.length > 1 ? split[1] : FREE_TALK.get(fromUser.getUserName());
            String key = message.isGroup() ? fromUser.getUserName() + message.getMember().getUserName() : fromUser.getUserName();
            switch (flag) {
                case "start", "开启" -> {
                    FREE_TALK.put(key, model);
                    this.context.getApi().sendText(fromUser.getUserName(), "已开启自由对话，模型：" + model);
                }
                case "end", "结束" -> {
                    FREE_TALK.remove(key);
                    this.context.getApi().sendText(fromUser.getUserName(), "已结束自由对话，模型：" + model);
                }
                case "status", "状态" ->
                        this.context.getApi().sendText(fromUser.getUserName(), "自由对话状态：" + (FREE_TALK.containsKey(fromUser.getUserName()) ? "开启，模型：" + model : "关闭"));
                default -> {
                }
            }
        } catch (Exception e) {
            log.error("指令处理失败", e);
            this.context.getApi().sendText(fromUser.getUserName(), "指令处理失败");
        }

    }

    /**
     * 处理groq请求
     *
     * @param content  内容
     * @param fromUser 来自用户
     */
    private void handleGroq(String content, Contact fromUser, OkHttpClient client) {
        String cacheKey = "groq::" + fromUser.getUserName();

        if (CLEAR_KEYWORDS.contains(content)) {
            CACHE.remove(cacheKey);
            this.context.getApi().sendText(fromUser.getUserName(), "已清空上下文");
            return;
        }

        List<JSONObject> cache = getCache(cacheKey);
        JSONObject question = JSONUtil.createObj().set("role", "user").set("content", content);
        cache.add(question);
        Request request = new Request.Builder().url(GROQ_URL).post(RequestBody.create(JSONUtil.createObj().set("messages", cache).toString(), MediaType.parse("application/json"))).build();
        try (Response response = client.newCall(request).execute()) {
            JSONObject result = JSONUtil.parseObj(response.body().string());
            JSONObject message = result.getByPath("choices.0.message", JSONObject.class);
            String resultString = message.getStr("content");
            resultString = fromUser.isGroup() ? content + "\n--------------\n" + resultString : resultString;
            this.context.getApi().sendText(fromUser.getUserName(), resultString + "\n--------------\n响应时间：" + result.getStr("api_duration"));
            cache.add(message);
        } catch (Exception e) {
            log.error("Groq请求失败", e);
            cache.remove(question);
            this.context.getApi().sendText(fromUser.getUserName(), "你这个问题我没法回答，真的");
        }
    }

    /**
     * 处理google gemini请求
     *
     * @param content  内容
     * @param fromUser 来自用户
     */
    private void handleGemini(String content, Contact fromUser, OkHttpClient client) {
        long start = Instant.now().toEpochMilli();
        String cacheKey = "gemini::" + fromUser.getUserName();

        if (CLEAR_KEYWORDS.contains(content)) {
            CACHE.remove(cacheKey);
            this.context.getApi().sendText(fromUser.getUserName(), "已清空上下文");
            return;
        }
        List<JSONObject> cache = getCache(cacheKey);
        JSONObject question = JSONUtil.createObj().set("role", "user").set("parts", JSONUtil.createArray().put(JSONUtil.createObj().set("text", content)));
        cache.add(question);
        HttpUrl url = HttpUrl.get(GEMINI_URL).newBuilder().addQueryParameter("q", JSONUtil.createObj().set("contents", cache).toString()).build();
        Request request = new Request.Builder().url(url).get().build();
        try (Response response = client.newCall(request).execute()) {
            JSONObject result = JSONUtil.parseObj(response.body().string());
            JSONObject object = result.getByPath("answer.candidates.0.content", JSONObject.class);
            String resultString = object.getByPath("parts.0.text", String.class);
            resultString = fromUser.isGroup() ? content + "\n--------------\n" + resultString : resultString;
            this.context.getApi().sendText(fromUser.getUserName(), resultString + "\n--------------\n响应时间：" + (Instant.now().toEpochMilli() - start / 1000.0) + "s");
            cache.add(object);
        } catch (Exception e) {
            log.error("Gemini请求失败", e);
            cache.remove(question);
            this.context.getApi().sendText(fromUser.getUserName(), "你这个问题我没法回答，真的");
        }
    }

    /**
     * 处理 GPT4 请求
     *
     * @param content  内容
     * @param fromUser 来自用户
     */
    private void handleGPT4(String content, Contact fromUser, OkHttpClient client) {
        String cacheKey = "gpt4::" + fromUser.getUserName();

        if (CLEAR_KEYWORDS.contains(content)) {
            CACHE.remove(cacheKey);
            this.context.getApi().sendText(fromUser.getUserName(), "已清空上下文");
            return;
        }

        List<JSONObject> cache = getCache(cacheKey);
        JSONObject question = JSONUtil.createObj().set("role", "user").set("content", content);
        cache.add(question);
        Request request = new Request.Builder().url(GPT4_URL).post(RequestBody.create(JSONUtil.createObj().set("messages", cache).toString(), MediaType.parse("application/json"))).build();
        try (Response response = client.newCall(request).execute()) {
            JSONObject result = JSONUtil.parseObj(response.body().string());
            JSONObject message = result.getByPath("choices.0.message", JSONObject.class);
            String resultString = message.getStr("content");
            resultString = fromUser.isGroup() ? content + "\n--------------\n" + resultString : resultString;
            this.context.getApi().sendText(fromUser.getUserName(), resultString + "\n--------------\n响应时间：" + result.getStr("api_duration"));
            cache.add(message);
        } catch (Exception e) {
            log.error("GPT4请求失败", e);
            cache.remove(question);
            this.context.getApi().sendText(fromUser.getUserName(), "你这个问题我没法回答，真的");
        }
    }

    /**
     * 处理 gpt3.5 请求，duckduckgo提供服务
     *
     * @param content  内容
     * @param fromUser 来自用户
     */
    private void handleDuckDuckGo(String content, Contact fromUser, OkHttpClient client) {
        String cacheKey = "duckduckgo::" + fromUser.getUserName();

        if (CLEAR_KEYWORDS.contains(content)) {
            CACHE.remove(cacheKey);
            this.context.getApi().sendText(fromUser.getUserName(), "已清空上下文");
            return;
        }

        List<JSONObject> cache = getCache(cacheKey);
        JSONObject question = JSONUtil.createObj().set("role", "user").set("content", content);
        cache.add(question);
        Request request = new Request.Builder().url(DUCKDUCKGO_URL).post(RequestBody.create(JSONUtil.createObj().set("messages", cache).toString(), MediaType.parse("application/json"))).build();
        try (Response response = client.newCall(request).execute()) {
            JSONObject result = JSONUtil.parseObj(response.body().string());
            JSONObject message = result.getByPath("choices.0.message", JSONObject.class);
            String resultString = message.getStr("content");
            resultString = fromUser.isGroup() ? content + "\n--------------\n" + resultString : resultString;
            this.context.getApi().sendText(fromUser.getUserName(), resultString + "\n--------------\n响应时间：" + result.getStr("api_duration"));
            cache.add(message);
        } catch (Exception e) {
            log.error("DuckDuckGo请求失败", e);
            cache.remove(question);
            this.context.getApi().sendText(fromUser.getUserName(), "你这个问题我没法回答，真的");
        }
    }

    /**
     * 处理AI绘图
     *
     * @param content 内容
     * @param from    请求来源
     */
    private void handleDraw(String content, Contact from) {
        File file = null;
        try {
            file = DrawService.draw(this.plugin.getDataFolder(), content);
            this.context.getApi().sendImage(from.getUserName(), file);
        } catch (Exception e) {
            log.error("绘图失败", e);
            this.context.getApi().sendText(from.getUserName(), "绘图失败");
        } finally {
            if (FileUtil.exist(file))
                FileUtil.del(file);
        }
    }
}
